<?php
namespace lib;

use sys\Log;

class LoginTokens extends \mexti\MinHeapNode implements \Serializable {
    const IDLE_TIMEOUT  = 7200; # 2小时

    public readonly string $token;
    public readonly \lib\User $user;
    public int $active;

    private static \mexti\MinHeap $mgr;
    private static int $mgrTickId = 0;

    protected static array $token_cache = [];

    # 序列化缓存文件
    protected static string $cache_file;

    public function __construct(string $token, \lib\User $user){
        $this->token = $token;
        $this->user = $user;
        $this->active = time();
        static::$token_cache[$this->token] = $this;
        static::$mgr->insert($this);
    }

    public function compare(\mexti\MinHeapNode $b): int
    {
        /** @var \lib\User $b */
        if($this->active > $b->active) return 1;
        return 0;
    }

    /**
     * 更新 根据token获取用户信息的同时, 更新活动时间.
     */
    public static function getUser(string $token) :?\lib\User
    {
        /** @var static $static */
        if(null !== ($static = static::$token_cache[ $token ] ?? null)){
            if($static->inHeap()){
                $static->active = time();
                $static->adjust();
            }
            return $static->user;
        }
        return null;
    }

    public static function get(string $token) :?static {
        if(null !== ($static = static::$token_cache[ $token ] ?? null)){
            if($static->inHeap()){
                $static->active = time();
                $static->adjust();
            }
            return $static;
        }
        return null;
    }

    // 清理自身
    protected function clear(){
        $token  = $this->token;
        unset(static::$token_cache[ $token ]);
        $db = new \sys\Db();
        $db->table('el_token')->where([['token', '=', $token],['plat_id','=', 0]])->delete();
    }

    public function setTimeout(bool $timeout){
        if(!$timeout){
            if($this->inHeap()){
                $this->erase();
            }
        }else{
            $this->active = time();
            if($this->inHeap()){
                $this->adjust();
            }else{
                static::$mgr->insert($this);
            }
        }
    }

    # 登出接口
    public function logout()
    {
        if(isset(static::$token_cache[ $this->token ])){
            if($this->inHeap()){
                $this->erase();
            }
            $this->clear();
        }
    }

    /**
     * 序列化, 系统退出前, 保存自身登录状态
     */
    public function serialize() : string {
        return serialize([
            $this->user->id, 
            $this->token,
            $this->active == 0 ? time() + 360: $this->active,
        ]);
    }

    public function unserialize(string $data) {
        list(
            $id, 
            $this->token,
            $this->active,
        ) = unserialize($data);
        
        if($this->active < time()) return;

        if(null !== ($user = \lib\User::get($id))){
            $this->user = $user;
            static::$token_cache[ $this->token ] = $this;
            static::$mgr->insert($this);
        }
    }

    public static function start(string $file)
    {
        # 载入用户、商户数据
        static::$mgr = new \mexti\MinHeap();

        static::$cache_file = $file;
        if(is_file($file)){
            unserialize(file_get_contents($file));
        }
        # 1. 创建一个 mgr 管理登录session
        # 2. 启动一个协程, 每隔一段时间, 检查一次登录状态, 如果超时, 则清理掉.
        
        static::$mgrTickId = \Swoole\Timer::tick(1000, function(){
            $now = time();
            try{
                while(!static::$mgr->isEmpty()){
                    /** @var \lib\LoginTokens $node */
                    $node = static::$mgr->top();
                    if($node->active > $now - static::IDLE_TIMEOUT){
                        break;
                    }
                    $node = static::$mgr->extract(); // 删除 $node;
                    /** @var \lib\LoginTokens $node */
                    $node->clear();
                }
            }catch(\Throwable $e){
                Log::console(implode([$e->getMessage(),"\nFILE: ", $e->getFile(), "\nLINE: ", $e->getLine(),"\nTRACE: ", $e->getTraceAsString(),"\n"]),'ERROR');
            }
        });
    }

    public static function shutdown()
    {
        # 序列化 用户组
        # 关闭协程
        \Swoole\Timer::clear(static::$mgrTickId);
        $contents = serialize([array_values(static::$token_cache)]);
        file_put_contents(static::$cache_file, $contents);
        Log::console("Store Cache....",'DEBUG');
    }
}
